/*!

@author
*/
#include <stdafx.h>

#include "Terrain.h"




Terrain::Terrain()
{
	m_pVertexBuffer = NULL;
	m_uiVertexCount	= 0;
	m_pIndexBuffer	= NULL;
	m_uiIndexCount	= 0;
	m_uiTerrainSize	= 512;
	
	m_pTextureHeightMap			= NULL;
	m_pTextureIndexMap			= NULL;
	m_pTextureArrayMaterials	= NULL;

	m_pd3dVertexShader		= NULL;
	m_pd3dPixelShader		= NULL;
	m_pd3dGeometryShader	= NULL;
	m_pd3dInputLayout		= NULL;

	
}

Terrain::~Terrain()
{
	Finalize();
}

bool Terrain::Initialize(GraphicDeviceD3D11* pDevice, const string& fileName, uint32 uiPatchSize)
{
	m_pGraphicDevice	= pDevice;
	CreateImmutableStateObjects();

	LoadTextures();

	CreateSharedVertexBuffer(uiPatchSize);

	CreateIndexBuffer(uiPatchSize);

	CreateShaders();

	uint32 uiPatchCount = m_uiTerrainSize / uiPatchSize;
	if(uiPatchCount > 1)
		uiPatchCount = 1;
	m_arrPatchesCB.resize(uiPatchCount);
	for(uint32 y = 0; y < uiPatchCount; y++)
	{
		m_arrPatchesCB[y].resize(uiPatchCount);

		for(uint32 x = 0; x < uiPatchCount; x++)
		{
			ConstantBuffer<PatchParameters>* pPatchCB	= new ConstantBuffer<PatchParameters>(pDevice->GetDevice());
			pPatchCB->GetPointerData()->dummy = 0;
			pPatchCB->GetPointerData()->scale = 1;
			pPatchCB->GetPointerData()->pos_x = (float)(x * uiPatchSize);
			pPatchCB->GetPointerData()->pos_z = (float)(y * uiPatchSize);
			pPatchCB->SetAsDirty(true);


			m_arrPatchesCB[y][x] = pPatchCB;
		}

	}


	
	return true;
}

//!
void Terrain::Finalize()
{
	for(size_t y = 0; y < m_arrPatchesCB.size(); y++)
	{
		for(size_t x = 0; x < m_arrPatchesCB[y].size(); x++)
		{
			_SAFE_DELETE(m_arrPatchesCB[y][x]);
		}
	}

	_SAFE_DELETE(m_pVertexBuffer);
	_SAFE_DELETE(m_pIndexBuffer);
	_SAFE_DELETE(m_pTextureHeightMap);
	_SAFE_DELETE(m_pTextureIndexMap);
	_SAFE_DELETE(m_pTextureArrayMaterials);

	_SAFE_RELEASE(m_pd3dVertexShader);
	_SAFE_RELEASE(m_pd3dPixelShader);
	_SAFE_RELEASE(m_pd3dGeometryShader);
	_SAFE_RELEASE(m_pd3dInputLayout);

}

//!
void Terrain::Render()
{
	ID3D11DeviceContext* pd3dContext = m_pGraphicDevice->GetImmediateContext();

	// Set Rasterizer
	pd3dContext->RSSetState(m_pd3dRasterizerState);

	// Set Depth Stencil State 
	pd3dContext->OMSetDepthStencilState(m_pd3dDepthStencilState, 0);

	// Set Blend State
	float fBlendFactor[] = {1,1,1,1};
	//pd3dContext->OMSetBlendState(m_pd3dBlendState, fBlendFactor, 0xffffffff);

	// Set Input Assembly Stuff
	{
		// Set Topology
		pd3dContext->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST);
		// Set Index Buffer
		pd3dContext->IASetIndexBuffer(m_pIndexBuffer->GetIndexBuffer(), DXGI_FORMAT_R16_UINT, 0);
		// Set Vertex Buffer
		UINT uiStride = sizeof(float) * 2;
		UINT uiOffset = 0;
		ID3D11Buffer* pd3dVB = m_pVertexBuffer->GetStream();
		pd3dContext->IASetVertexBuffers(0, 1, &pd3dVB, &uiStride, &uiOffset);

		// Set Input Layout
		pd3dContext->IASetInputLayout(m_pd3dInputLayout);
	}

	// Set Vertex Shader Stuff
	{
		ID3D11ShaderResourceView* pResources[2];
		pResources[0] = m_pTextureHeightMap->GetResourceView();
		pResources[1] = m_pTextureIndexMap->GetResourceView();

		pd3dContext->VSSetShaderResources(0, 2, pResources);
		pd3dContext->VSSetShader(m_pd3dVertexShader, NULL, 0);
	}

	// Set Geometry Shader Stuff
	{
		pd3dContext->GSSetShader(m_pd3dGeometryShader, NULL, 0);
	}

	// Set Pixel Shader Stuff
	{
		ID3D11ShaderResourceView* pResources =  m_pTextureArrayMaterials->GetResourceView();
		pd3dContext->PSSetShaderResources(2, 1, &pResources);
		pd3dContext->PSSetShader(m_pd3dPixelShader, NULL, 0);
	}


	for(size_t y = 0; y < m_arrPatchesCB.size(); y++)
	{
		for(size_t x = 0; x < m_arrPatchesCB[y].size(); x++)
		{
			ConstantBuffer<PatchParameters>* pPatchCB = m_arrPatchesCB[y][x];
			if(pPatchCB->IsDirty())
				pPatchCB->Upload(pd3dContext);

			pd3dContext->VSSetConstantBuffers(1, 1, pPatchCB->GeConstantBuffers());
			//pd3dContext->PSSetConstantBuffers(1, 1, pPatchCB->GeConstantBuffers());

			// Make Draw Primitives
			pd3dContext->DrawIndexed(m_uiIndexCount, 0, 0);
		}

	}


}


/*!

*/
void Terrain::CreateSharedVertexBuffer(uint32 uiVertexSideCount)
{
	float fCellSize = 1.0f;//(float)(1.0 / (double)(uiVertexSideCount-1));
	//	
	uint32 uiVertexCount	 = uiVertexSideCount * uiVertexSideCount;
	
	Vertex* pVertices = new Vertex[uiVertexCount];
	int		nIndex		= 0;
	for( uint32 h = 0; h < uiVertexSideCount; ++h )
		for( uint32 w = 0; w < uiVertexSideCount; ++w )
		{
			pVertices[nIndex].x	= (float)w * fCellSize;
			pVertices[nIndex].z	= (float)h * fCellSize;
			nIndex++;
		}

	// Create Dummy VB
	m_pVertexBuffer = new VertexBufferD3D11(m_pGraphicDevice->GetDevice());
	m_pVertexBuffer->Create(sizeof(Vertex), uiVertexCount, pVertices);

	_SAFE_DELETE_ARRAY(pVertices);

	m_uiVertexCount = uiVertexCount;
}

/*!

*/
bool Terrain::CreateIndexBuffer(uint16 uiPatchBlockCount)
{
	vector<uint16> lstIndices;
	uint16 uiVertexSideCount = uiPatchBlockCount;
	uint16 uiSideCount		 = uiVertexSideCount - 1;

	// 
	for(uint16 y = 0; y < uiSideCount; y++)
	{
		uint16 y_start = uiVertexSideCount * y;
		uint16 y_next  = uiVertexSideCount * (y+1);
		for(uint16 x = 0; x < uiSideCount; x++)
		{
			lstIndices.push_back(x + y_start + 0);
			lstIndices.push_back(x + y_start + 1);
			lstIndices.push_back(x + y_next + 1);

			lstIndices.push_back(x + y_next + 0);
			lstIndices.push_back(x + y_start + 0 );
			lstIndices.push_back(x + y_next + 1);
		}
	}

	uint32	uiIndexCount = (uint32)lstIndices.size();

	
	// Creo el Index Buffer en cuestion
	m_pIndexBuffer = new IndexBufferD3D11(m_pGraphicDevice->GetDevice());

	IBDescriptor ibDesc(sizeof(uint16), uiIndexCount, false);
	if(!m_pIndexBuffer->Create(ibDesc, &lstIndices[0]))
		return false;

	m_uiIndexCount	= (int)lstIndices.size();

	return true;
};


//! 
bool Terrain::CreateImmutableStateObjects()
{
	// Blend State
	memset(&m_descBlendState, 0, sizeof(m_descBlendState));
	m_descBlendState.AlphaToCoverageEnable					= FALSE;
	m_descBlendState.IndependentBlendEnable					= FALSE;
	m_descBlendState.RenderTarget[0].BlendEnable			= FALSE;
	m_descBlendState.RenderTarget[0].RenderTargetWriteMask	= D3D11_COLOR_WRITE_ENABLE_ALL;
	m_pGraphicDevice->GetDevice()->CreateBlendState(&m_descBlendState, &m_pd3dBlendState);

	// Depth Stencil State
	memset(&m_descDepthStencilState, 0, sizeof(m_descDepthStencilState));
	m_descDepthStencilState.DepthEnable		= TRUE;
	m_descDepthStencilState.DepthWriteMask	= D3D11_DEPTH_WRITE_MASK_ALL;
	m_descDepthStencilState.DepthFunc		= D3D11_COMPARISON_LESS_EQUAL;
	m_descDepthStencilState.StencilEnable	= FALSE;
	m_pGraphicDevice->GetDevice()->CreateDepthStencilState(&m_descDepthStencilState, &m_pd3dDepthStencilState);

	// Rasterizer State
	memset(&m_descRasterizer, 0, sizeof(m_descRasterizer));
	m_descRasterizer.FillMode				= D3D11_FILL_SOLID;
	m_descRasterizer.CullMode				= D3D11_CULL_FRONT;
	m_descRasterizer.FrontCounterClockwise	= FALSE;
	m_descRasterizer.DepthBias				= 0;
	m_descRasterizer.DepthBiasClamp			= 0;
	m_descRasterizer.SlopeScaledDepthBias	= 0;
	m_descRasterizer.DepthClipEnable		= TRUE;
	m_descRasterizer.ScissorEnable			= FALSE;
	m_descRasterizer.MultisampleEnable		= FALSE;
	m_descRasterizer.AntialiasedLineEnable	= FALSE;

	m_pGraphicDevice->GetDevice()->CreateRasterizerState(&m_descRasterizer, &m_pd3dRasterizerState);

	return true;
}

//! 
bool Terrain::LoadTextures()
{
	// Create Heightmap from RAW
	m_pTextureHeightMap = new TextureD3D11(m_pGraphicDevice->GetDevice());
	m_pTextureHeightMap->CreateFromFile("../textures/heightmap.jpg");

	m_uiTerrainSize = m_pTextureHeightMap->GetDescriptor().Width;

	// Load Slices on Texture Array
	vector<string> lstMaterials;

	DirectoryFilter dirFilter;

	string path = "../textures/materials/";
	dirFilter.Initialize(path, "*.dds", true, false);

	if(dirFilter.Count() == 0)
	{
		path = "../textures/terrain_textures/";
		dirFilter.Initialize(path, "*.png", true, false);
	}

	for(DirectoryFilter::Iterator it = dirFilter.Begin(); it != dirFilter.End(); it++)
	{
		lstMaterials.push_back(path + *it);
	}
	
	uint8 arrayCount = (uint8)lstMaterials.size();

	// Create Index Map
	D3D11_TEXTURE2D_DESC descTextureIndices;
	descTextureIndices.Width				= m_pTextureHeightMap->GetDescriptor().Width;
	descTextureIndices.Height				= m_pTextureHeightMap->GetDescriptor().Height;
	descTextureIndices.MipLevels			= 1;
	descTextureIndices.ArraySize			= 1;
	descTextureIndices.Format				= DXGI_FORMAT_R8_UNORM;
	descTextureIndices.SampleDesc.Count		= 1;
	descTextureIndices.SampleDesc.Quality	= 0;
	descTextureIndices.Usage				= D3D11_USAGE_IMMUTABLE;
	descTextureIndices.BindFlags			= D3D11_BIND_SHADER_RESOURCE;
	descTextureIndices.CPUAccessFlags		= 0;
	descTextureIndices.MiscFlags			= 0;

	int totalIndexCount = descTextureIndices.Width * descTextureIndices.Height;
	uint8* pIndexMapData = new uint8[totalIndexCount];
	memset(pIndexMapData, 0, descTextureIndices.Width * descTextureIndices.Height * sizeof(uint8));

	for(int i = 0; i < 10000; i++)
	{
		uint16 w = 2 + rand() % 4;
		uint16 h = 2 + rand() % 4;

		uint16 posX = rand() % (descTextureIndices.Width-w);
		uint16 posY = rand() % (descTextureIndices.Height-h);

		uint8 materialID = rand() % arrayCount;

		// Draw a Quad 2x2
		for(uint16 y = 0; y < w; y++)
		{
			for(uint16 x = 0; x < h; x++)
			{
				int index = ((posY + y) * descTextureIndices.Width) + posX + x;

				if(index < totalIndexCount)
					pIndexMapData[index] = materialID;
			}
		}
	}

	m_pTextureIndexMap = new TextureD3D11(m_pGraphicDevice->GetDevice());
	m_pTextureIndexMap->CreateFromRAW(descTextureIndices, pIndexMapData);
	_SAFE_DELETE_ARRAY(pIndexMapData);


	// Create Texture Array
	D3D11_TEXTURE2D_DESC descTextureArray;
	descTextureArray.Width				= 512;
	descTextureArray.Height				= 512;
	descTextureArray.MipLevels			= 8;
	descTextureArray.ArraySize			= arrayCount;
	descTextureArray.Format				= DXGI_FORMAT_R8G8B8A8_UNORM;
	descTextureArray.SampleDesc.Count	= 1;
	descTextureArray.SampleDesc.Quality	= 0;
	descTextureArray.Usage				= D3D11_USAGE_DEFAULT;
	descTextureArray.BindFlags			= D3D11_BIND_SHADER_RESOURCE;
	descTextureArray.CPUAccessFlags		= 0;
	descTextureArray.MiscFlags			= 0;

	m_pTextureArrayMaterials = new TextureD3D11(m_pGraphicDevice->GetDevice());
	m_pTextureArrayMaterials->Create(descTextureArray);
		
	for(size_t i = 0; i < lstMaterials.size(); i++)
		m_pTextureArrayMaterials->LoadTextureOnArray(m_pGraphicDevice->GetImmediateContext(), i, lstMaterials[i]);
	return false;
}



ID3DBlob* CompileShader(const string& fileName, const string& entryPoint, const string& target)
{
	ID3DBlob*	pblobShader = NULL;
	ID3DBlob*	pblobErrors = NULL;
	//DWORD		dwFlags1	= D3D10_SHADER_DEBUG | D3D10_SHADER_OPTIMIZATION_LEVEL0;//D3D10_SHADER_OPTIMIZATION_LEVEL3;//
	DWORD		dwFlags1	= D3D10_SHADER_DEBUG | D3D10_SHADER_ENABLE_STRICTNESS | D3D10_SHADER_OPTIMIZATION_LEVEL3;
	DWORD		dwFlags2	= 0;

	// TODO: Faltan las macros y los includes
	D3D10_SHADER_MACRO*	pDefines	= NULL;

	HRESULT	hr = D3DX11CompileFromFileA(fileName.c_str(), pDefines, NULL, entryPoint.c_str(), target.c_str(), dwFlags1, dwFlags2, NULL, &pblobShader, &pblobErrors, NULL);

	if(FAILED(hr))
	{
		if(pblobErrors)
		{	
			printf("Error compiling shader: %s \n %s\n", fileName.c_str(), (LPCSTR)pblobErrors->GetBufferPointer());
			_SAFE_RELEASE(pblobShader);
		}
		else
		{
			printf("Error compiling shader: %s \n No Error BLOB\n", fileName.c_str());

		}
	}
	_SAFE_RELEASE(pblobErrors);

	return pblobShader;
}


//! 
bool Terrain::CreateShaders()
{
	// Compile Shaders
	ID3DBlob*	pblobPixelShader	= CompileShader("../shaders/terrain.hlsl", "PS_Terrain", "ps_4_0");
	ID3DBlob*	pblobVertexShader	= CompileShader("../shaders/terrain.hlsl", "VS_Terrain", "vs_4_0");
	ID3DBlob*	pblobGeometryShader = CompileShader("../shaders/terrain.hlsl", "GS_Terrain", "gs_4_0");

	bool bRtn = true;
	if(!pblobPixelShader || !pblobGeometryShader || !pblobVertexShader)
		bRtn = false;
	else
	{

	}

	HRESULT hr;
	// Create Pixel Shader
	hr = m_pGraphicDevice->GetDevice()->CreatePixelShader(pblobPixelShader->GetBufferPointer(), pblobPixelShader->GetBufferSize(), NULL, &m_pd3dPixelShader);
	if(FAILED(hr))
		bRtn = false;

	// Create Geometry Shader
	hr = m_pGraphicDevice->GetDevice()->CreateGeometryShader(pblobGeometryShader->GetBufferPointer(), pblobGeometryShader->GetBufferSize(), NULL, &m_pd3dGeometryShader);
	if(FAILED(hr))
		bRtn = false;

	// Create Vertex Shader & InputLayout
	hr = m_pGraphicDevice->GetDevice()->CreateVertexShader(pblobVertexShader->GetBufferPointer(), pblobVertexShader->GetBufferSize(), NULL, &m_pd3dVertexShader);
	if(FAILED(hr))
		bRtn = false;

	D3D11_INPUT_ELEMENT_DESC layout[] = 
	{
		{ "POSITION",	0, DXGI_FORMAT_R32G32_FLOAT, 0,  0, D3D11_INPUT_PER_VERTEX_DATA, 0 },  
	};	
	// Create Input Layout

	hr = m_pGraphicDevice->GetDevice()->CreateInputLayout(layout, 1, pblobVertexShader->GetBufferPointer(), pblobVertexShader->GetBufferSize(), &m_pd3dInputLayout);
	if(FAILED(hr))
		bRtn = false;

	_SAFE_RELEASE(pblobPixelShader);
	_SAFE_RELEASE(pblobVertexShader);
	_SAFE_RELEASE(pblobGeometryShader);
	return bRtn;
}
